package indigo.shiro.config;

import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.util.ClassUtils;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.AuthenticationFilter;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;

public class ShiroFilterRegistrar implements ImportBeanDefinitionRegistrar {
	public static final String DEFAULT_SECURITY_MANAGER_BEAN_NAME = "securityManager";
	public static final String DEFAULT_LOGIN_URL = AccessControlFilter.DEFAULT_LOGIN_URL;
	public static final String DEFAULT_SUCCESS_URL = AuthenticationFilter.DEFAULT_SUCCESS_URL;

	protected Map<String, Filter> getFilters(AnnotationAttributes attrs) {
		Map<String, Filter> filters = Maps.newHashMap();
		AnnotationAttributes[] filterAttrs = attrs.getAnnotationArray("filters");
		if (filterAttrs.length > 0) {
			for (AnnotationAttributes filterAttr : filterAttrs) {
				String name = filterAttr.getString("name");
				Class<? extends Filter> type = filterAttr.getClass("type");
				Filter filter = (Filter) ClassUtils.newInstance(type);
				filters.put(name, filter);
			}
		}
		return filters;
	}

	protected Map<String, String> getFilterChainDefinitionMap(AnnotationAttributes attrs) {
		Map<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap(); // order matters!
		AnnotationAttributes[] filterChainAttrs = attrs.getAnnotationArray("filterChains");
		if (filterChainAttrs.length > 0) {
			for (AnnotationAttributes filterChainAttr : filterChainAttrs) {
				String name = filterChainAttr.getString("name");
				String definition = filterChainAttr.getString("definition");
				String value = filterChainDefinitionMap.get(name);
				if (Strings.isNullOrEmpty(value)) {
					value = definition;
				} else {
					value = value + "," + definition;
				}
				filterChainDefinitionMap.put(name, definition);
			}
		}
		return filterChainDefinitionMap;
	}

	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		AnnotationAttributes attrs = AnnotationAttributes.fromMap(
				importingClassMetadata.getAnnotationAttributes(EnableShiroFilter.class.getName()));

		String loginUrl = Strings.emptyToNull(attrs.getString("loginUrl"));
		String successUrl = Strings.emptyToNull(attrs.getString("successUrl"));
		String unauthorizedUrl = Strings.emptyToNull(attrs.getString("unauthorizedUrl"));
		String securityManagerRef = Strings.emptyToNull(attrs.getString("securityManagerRef"));
		Map<String, Filter> filters = getFilters(attrs);
		Map<String, String> filterChainDefinitionMap = getFilterChainDefinitionMap(attrs);

		AbstractBeanDefinition definition = BeanDefinitionBuilder
				.genericBeanDefinition(ShiroFilterFactoryBean.class)
				.addPropertyValue("loginUrl", loginUrl)
				.addPropertyValue("successUrl", successUrl)
				.addPropertyValue("unauthorizedUrl", unauthorizedUrl)
				.addPropertyValue("filters", filters)
				.addPropertyValue("filterChainDefinitionMap", filterChainDefinitionMap)
				.addPropertyReference("securityManager", securityManagerRef)
				.getBeanDefinition();
		registry.registerBeanDefinition("shiroFilter", definition);
	}
}
